#include "flow3ddata.h"

#include <iostream>
#include <fstream>
#include <sstream>

#include <gmCore>
using GMlib::Vector3D;
using GMlib::Point;
using std::ios;
using std::cout;
using std::endl;
using std::string;
using std::ofstream;
using std::ifstream;

using namespace GMlib;

Flow3DData::Flow3DData() {}


int Flow3DData::_get_grid_size(std::istringstream &iss)
{
	int grid_lower_x, grid_lower_y, grid_lower_z;
	int grid_upper_x, grid_upper_y, grid_upper_z;

	//std::string dummy_str;
	float dummy;
	int frame_nr;
	double t;

	//   1    1    0.000E+00    0.000E+00    3   62    2   21    3   36
	iss >> frame_nr >> dummy >> t >> dummy >> grid_lower_x >> grid_upper_x >> grid_lower_y >> grid_upper_y >> grid_lower_z >> grid_upper_z;

	grid_size_x = grid_upper_x - grid_lower_x + 1;
	grid_size_y = grid_upper_y - grid_lower_y + 1;
	grid_size_z = grid_upper_z - grid_lower_z + 1;
	int grid_siz = grid_size_x * grid_size_y * grid_size_z;
	return grid_siz;
}

// ! Optimized Flow3D data loading. Read chunks into a buffer
void Flow3DData::_fast_load( const char* filename ) {
	_valid = false;
	_fraction_of_fluid_data.clear();
	_pressure_data.clear();
	_velocity_data.clear();
	
	// open file
	std::ifstream file( filename, std::ios::in | std::ios::binary ); 

	// Quit program if file not found
	if(!file)
	{
		return;
	}

	// Make a buffer to store a line from the file

	// use stringstream to parse floating-point numbers from file
	std::istringstream iss;

	// ignore first few lines. 
	//flscon:  version 9.4   win32-ifl 2009  
	//					 FLOW-3D   14:59:40  09/01/2010 yhah     
	//hydr3d:   version  9.4   win32-ifl 2009                    
	//					3-D flow over a standard rectangular weir.                                      
	//					5
	//hydr3d:   version  9.4   win32-ifl 2009          
	//					printing f, u, v and w       t=0.0  ix=3 to  62   jy=2 to  21  kz=3 to  36 
	//					1    1    0.000E+00    0.000E+00    3   62    2   21    3   36
	//					x              y              z             f              u              v              w            
	for(int i=0; i<7; i++)
	{
		char temp[256];
		file.getline(temp,256);
	}


	std::string lineread;
	std::getline(file, lineread);
	// Set string buffer to new line
	//iss.seekg(std::ios::beg);
	//iss.str("");
	iss.clear ();
	iss.str(lineread);
	//std::cout << iss.str() << std::endl;
	int grid_total_size = _get_grid_size(iss);
	printf("%i \n", grid_total_size );


	int active_frame = 0;

	maxVel = 0.0f;
	float avgVel = 0.0f;

	char temp[256];
	while( std::getline(file, lineread) )
	{
		printf("frame %i \n", active_frame);
		std::cout << lineread << std::endl;

		// Read frame
		//_fraction_of_fluid_data.resize( (active_frame+1) * grid_total_size);


		// Read a line and parse it
		//while( !inf.getline(temp, 256).eof() )

		std::string lineread;
		int lines_read = 0;
		while( lines_read < grid_total_size )
		{
			std::getline(file, lineread);
			iss.clear ();
			iss.str(lineread);
			// Variables to fetch
			float datx, daty, datz, datf, datu, datv, datw;
			float datp;
			float datVf;

			// Get variables
			iss >> datx >> daty >> datz >> datf >> datu >> datv >> datw;
			//iss >> datx >> daty >> datz >> datp >> datf >> datu >> datv >> datw;
			//iss >> datx >> daty >> datz >> datf >> datu >> datv >> datw >> datVf;
			//iss >> datx >> daty >> datz >> datf >> datVf;
			//printf("%f %f %f\n",datu,datv,datw);
			_fraction_of_fluid_data.push_back( datf );
			//float vels[] = {datu,datv,datw};

			_velocity_data.push_back( Vector<float,3>( Vector3D<float>(datu,datv,datw) ) );
			float absVelocity = fabs( _velocity_data.back().getLength() );
			avgVel+=absVelocity;
			if( absVelocity > maxVel )
			{
				maxVel = absVelocity;
			}

			lines_read++;
		}

		for(int i=0; i<2; i++)
		{
			char temp[256];
			file.getline(temp,256);
			std::cout << temp << std::endl;
		}
		active_frame++;

		if(active_frame > 10) break;
	}

	avgVel /= _velocity_data.size();
	//float diff = maxVel - avgVel;
	maxVel *= 0.25;


	_num_frames = active_frame-1;
	std::cout << "_num_frames " << _num_frames << std::endl;

	std::cout << "maxVel: " <<  maxVel << std::endl;
	std::cout << "avgVel: " <<  avgVel << std::endl;

	// If data was formatted as expected, its valid

	_valid = true;
}

//! Parses header and counts number of frames
void Flow3DData::_parse_header(std::istringstream &ss)
{
	// store num frames, grid size x,y,z
	_num_frames = 0;
	int grid_lower_x, grid_lower_y, grid_lower_z;
	int grid_upper_x, grid_upper_y, grid_upper_z;


	//Read header
	//count number of frames by counting each occurence of "printing"
	//todo: would it be faster to dyn. resize data on-the-fly instead of reading num frames here? 
	//Or would it be faster to calc num of frames from filesize, gridsize and num variables? x,y,z,u,v,w,f,p (8 doubles)

	bool is_header_parsed = false;
	string w;
  while( ss >> w ) {
		// Read a token from ss, it may be seperated by spaces/tabs
		// We are only interested in the first word, so ignore all following chars until newline
    ss.ignore( 256, '\n' );

    if( !w.compare( "printing" ) )
		{
			// Each occurence of "printing" means its a new frame, total num_frames can be calced from gridsiz and filesiz.
      _num_frames++;

			if( !is_header_parsed )
			{
				is_header_parsed = true;
				string dummy_str;
				int frame_nr;
				double t;

				ss >> frame_nr >> dummy_str >> t >> dummy_str >> grid_lower_x >> grid_upper_x >> grid_lower_y >> grid_upper_y >> grid_lower_z >> grid_upper_z;

				grid_size_x = grid_upper_x - grid_lower_x + 1;
				grid_size_y = grid_upper_y - grid_lower_y + 1;
				grid_size_z = grid_upper_z - grid_lower_z + 1;

				int grid_siz = grid_size_x * grid_size_y * grid_size_z;

				// Ignore actual data
				for(int i=0; i<grid_siz; i++)
				{
						string w;
						ss >> w;
						ss.ignore( 256, '\n' );
				}
				
				//return; // TODO. breaks standard/slow loader
			}
    }
  }

	cout << "_num_frames counted: = " << _num_frames << endl;

}

void Flow3DData::_frame_from_stringstream( std::istringstream &ss, int frame )
{
	while( frame < _num_frames ) 
	{
		string w;
		ss >> w;
		ss.ignore( 256, '\n' );
		cout << "w: " << w << endl;


		cout << "frame " << frame << "/" << (_num_frames-1) << endl;
    
	int grid_total_size = grid_size_x * grid_size_y * grid_size_z;

  bool found_grid_spacing = false;
  bool gotGridOrigin = false;

	// Ignore 2 lines
  //  1    1    0.000E+00    0.000E+00    3   62    2   21    3   36 <-------------------------
  //     x              y              z             f              u              v              w    <---------------
	//  -9.7500000E+00  2.5000000E-01  2.5000000E-01  1.0000000E+00  2.0000000E+01  0.0000000E+00  0.0000000E+00 <---- Line we want to read

  for(int i=0; i<2; i++)
  {
      string w;
      ss >> w;
      ss.ignore( 256, '\n' );
  }

	int x=0;
	int y=0;
	int z=0;
  for( int j=0; j < grid_total_size; j++ ) 
	{
		// Todo. Generalize loader so datatypes u,v,w,f,p,t can occur in any order
    double datx, daty, datz, datf, datu, datv, datw;
    ss >> datx >> daty >> datz >> datf >> datu >> datv >> datw;
    ss.ignore( 256, '\n' );

    // We need the distance between cells in the grid. The grid isnt
    // necessarily uniform
    if( !found_grid_spacing )
    {
        if( !gotGridOrigin )
        {
            originX = datx;
            originY = daty;
            originZ = datz;
            gotGridOrigin = true;
        }
        else
        {
            gridSpaceX = datx - originX;
            gridSpaceY = daty - originY;
            gridSpaceZ = datz - originZ;
            found_grid_spacing = true;
            cout << "grid spaceing: " << gridSpaceX << " " << gridSpaceY << " " << gridSpaceZ << endl;
        }
    }
		//_fraction_of_fluid_data.at(frame * grid_total_size + j) = datf;
		int idx = x + y*grid_size_x + z*grid_size_x * grid_size_y + frame * grid_total_size;
		_fraction_of_fluid_data.at( idx ) = datf;

		x++;
		if( x > grid_size_x-1 )
		{
			x=0;
			y++;

			if( y > grid_size_y-1 )
			{
				y = 0;
				z++;
			}
		}

  }

	frame++;
	}//eof while

}

void Flow3DData::_loadData( const char* filename ){	
	_num_frames = 0;
	_valid = false;

	std::ifstream stream( filename, std::ios::in | std::ios::binary );
	if( !stream )
	{
		std::cerr << "  File could not be opened." << endl;
		return;
	}
	std::streampos stream_length;

	// Get file size
	stream.seekg( 0, ios::end );
	stream_length = stream.tellg();
	stream.seekg( 0, ios::beg );

	// Allocate char buffer same size as file and copy data to it
	std::vector<char> buffer( stream_length ); 
	stream.read( &buffer[0], stream_length );
	stream.close();

	// create a stringstream that points to the char buffer
	std::istringstream ss( &buffer[0] ); 
	ss.seekg(0,ios::beg);
	cout << "stringstream pos: " << ss.tellg() << endl;

	_parse_header( ss );

	// Set dimensions of actual data structure
  int grid_total_size = grid_size_x * grid_size_y * grid_size_z;
	_fraction_of_fluid_data.resize(_num_frames * grid_total_size);

  // Begin from start and read actual flow3d celldata
  ss.clear();
	ss.seekg(0,ios::beg);

  /* ignore first 5 lines in Flow3D file, they look like this:

	  flscon:  version 9.4   win32-ifl 2009  
	 FLOW-3D   14:59:40  09/01/2010 yhah     
		hydr3d:   version  9.4   win32-ifl 2009                    
	 3-D flow over a standard rectangular weir.                                      
			5
		hydr3d:   version  9.4   win32-ifl 2009     
	*/

  for(int i=0; i<6; i++)
  {
      string w;
      ss >> w;
      ss.ignore( 256, '\n' );
  }

  int current_frame = 0;

	_frame_from_stringstream(ss,0);
	//string w;
 // while( current_frame < _num_frames ) 
	//{
	//	ss >> w;
 //   ss.ignore( 256, '\n' );

	//	// Read a complete frame
	//	_frame_from_stringstream( ss, current_frame );
 //   cout << "frame " << current_frame << "/" << (_num_frames-1) << endl;
 //   current_frame++;
 // }

  _num_frames > 0 ? _valid = true : _valid = false;
  cout << "finished, valid:" << _valid << endl;
}

 std::vector< double >const& Flow3DData::getFractionOfFluid()
 {
	 return _fraction_of_fluid_data;
 }

 float Flow3DData::getAbsVelocityMagnitudeAt( int frame, int x, int y, int z )
 {
	 int tot_grid_cells = grid_size_x * grid_size_y * grid_size_z;
	 size_t idx = frame*tot_grid_cells + x + y*grid_size_x + z*grid_size_x*grid_size_y;
	 //if( _velocity_data.size() >= idx )
	 //{
		return fabs( _velocity_data[ idx ].getLength() ) / maxVel;
	 //}else{
		// return -1.0f; // todo: throw exception?
	 //}
 }

 Vector<int,3> Flow3DData::getGridDimensions()
 {
     int dim[] = {grid_size_x, grid_size_y, grid_size_z};
     return Vector<int,3>( dim );
 }


bool Flow3DData::isValid() const {

  return _valid;
}


void Flow3DData::load( const char* filename, DATA_TYPE type ) {

  _data_type = type;

  switch( _data_type ) {

  case DATA_TYPE_ALL:
		_fast_load(filename);
    //_loadData( filename );
    break;

    default:
      _valid = false;
      break;
  }
}

void Flow3DData::_deserialize(const char* filename)
{
}

void Flow3DData::_serialize(const char* filename)
{
}

int Flow3DData::getNumFrames()
{
	return _num_frames;
}


